#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import json
import time
import shutil
import socket
import subprocess
import http.client
import urllib.parse
import urllib.error
import urllib.request

import mirrors.plugin


class Main:

    def __init__(self, sock):
        self.URL = "http://wallpaper.apc.360.cn/index.php"      # it sucks that https://wallpaper.apc.360.cn returns 503 and has invalid certificate

        self.sock = sock
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.p = InfoPrinter()

    def run(self):
        # get categories
        self.p.print("get categories")
        categoryDict = {}
        if True:
            data = self._getJson(self.URL + "?c=WallPaper&a=getAllCategoriesV2")
            if data["errno"] != "0":
                raise Exception("get category failed, %s" % (data["errmsg"]))
            for item in data["data"]:
                categoryDict[item["id"]] = item["name"]
            time.sleep(5.0)

        # get total count of every category
        categoryTotal = {}
        sumTotal = None
        for categoryId, categoryName in categoryDict.items():
            self.p.print("get wallpaper count for category \"%s\"" % (categoryName))
            if True:
                data = self._getJson(self.URL + "?c=WallPaper&a=getAppsByCategory&cid=%s&start=%d&count=1" % (categoryId, 1))
                if data["errno"] != "0":
                    raise Exception("get total count of wallpapers in category (%s, %s) failed, %s" % (categoryId, categoryName, data["errmsg"]))
                categoryTotal[categoryId] = int(data["total"])
                sumTotal = sum(categoryTotal.values())
                time.sleep(5.0)

        # get wallpapers
        self.p.print("get wallpapers")
        self.p.incIndent()
        try:
            processed = 0
            progress = 0
            for categoryId, categoryName in categoryDict.items():
                start = 0
                while start < categoryTotal[categoryId]:
                    data = self._getJson(self.URL + "?c=WallPaper&a=getAppsByCategory&cid=%s&start=%d&count=100" % (categoryId, start))
                    if data["errno"] != "0":
                        raise Exception("get wallpaper of category (%s, %s), start from %d failed, %s" % (categoryId, categoryName, start, data["errmsg"]))
                    if int(data["total"]) != categoryTotal[categoryId]:
                        raise Exception("total count not match for category (%s, %s), start from %d" % (categoryId, categoryName, start))
                    if len(data["data"]) == 0:
                        raise Exception("no data returned for category (%s, %s), start from %d" % (categoryId, categoryName, start))

                    for item in data["data"]:
                        tags = item.get("utag", "")                         # it sucks that there maybe no "utag"
                        tags = Util.removeDuplicateSpace(tags)              # it sucks that there maybe duplicate spaces in "utag"

                        url = item["url"]
                        if url.startswith("tp://"):                         # it really sucks that...
                            url = "ht" + url
                        if True:
                            m = re.fullmatch("url: *(.*)", url)             # it really sucks that...
                            if m is not None:
                                url = m.group(1)
                        if url.startswith("http:/http://"):                 # it really sucks that...
                            url = url[len("http:/"):]

                        # find wallpaper
                        self.p.print("wallpaper %s found, resolution %s, category \"%s\", tags \"%s\"" % (item["id"], item["resolution"], categoryName, tags))
                        self.p.incIndent()
                        try:
                            dstDir = os.path.join(self.dataDir, item["id"])
                            if os.path.isdir(dstDir):
                                self.p.print("already exists")
                                processed, progress = Util.stepProgress(self.sock, sumTotal, processed, progress)   # step progress
                                continue

                            # create tmp directory
                            tmpDir = os.path.join(self.dataDir, ".download")
                            Util.forceMakeDir(tmpDir)

                            # write category & tags
                            with open(os.path.join(tmpDir, "category"), "w") as f:
                                f.write(categoryName)
                            with open(os.path.join(tmpDir, "tags"), "w") as f:
                                f.write(tags)

                            # download
                            self.p.print("downloading from %s" % (url))
                            imageFile = os.path.join(tmpDir, "%s_%s.%s" % (item["id"], item["resolution"], url.split(".")[-1]))
                            try:
                                self._downloadFile(url, imageFile)
                            except subprocess.CalledProcessError as e:
                                if e.returncode == 8:                               # not found, it sucks that there's broken links
                                    self.p.print("no resource to download")
                                    shutil.rmtree(tmpDir)
                                    processed, progress = Util.stepProgress(self.sock, sumTotal, processed, progress)   # step progress
                                    continue
                                raise

                            # rename tmpdir
                            os.rename(tmpDir, dstDir)

                            # step progress
                            processed, progress = Util.stepProgress(self.sock, sumTotal, processed, progress)
                        finally:
                            self.p.decIndent()

                    start += len(data["data"])
                    time.sleep(5.0)
        finally:
            self.p.decIndent()

    def _getJson(self, url):
        while True:
            try:
                resp = urllib.request.urlopen(url, timeout=60)
                return json.load(resp)
            except socket.timeout as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except http.client.HTTPException as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.HTTPError as e:
                if e.code == 404:
                    raise
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.URLError as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)

    def _downloadFile(self, url, localFile):
        while True:
            try:
                subprocess.check_call(["wget", "--quiet", "--no-check-certificate", "-O", localFile, url])      # always re-dowloand
                break
            except subprocess.CalledProcessError as e:
                if e.returncode == 8:       # not found
                    raise
                self.p.print("download failed and try again: %s" % str(e))
                time.sleep(60)


class Util:

    @staticmethod
    def removeDuplicateSpace(s):
        s2 = s.replace("  ", " ")
        while s != s2:
            s = s2
            s2 = s.replace("  ", " ")
        return s

    @staticmethod
    def stepProgress(sock, total, lastProcessed, lastProgress):
        newProcessed = lastProcessed + 1
        newProgress = newProcessed * 100 // total
        if newProgress > lastProgress:
            sock.progress_changed(newProgress)
        return (newProcessed, newProgress)

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMakeDir(path):
        if not os.path.exists(path):
            os.mkdir(path)
        elif not os.path.isdir(path):
            Util.forceDelete(path)
            os.mkdir(path)
        else:
            pass


class InfoPrinter:

    def __init__(self):
        self.indent = 0

    def incIndent(self):
        self.indent = self.indent + 1

    def decIndent(self):
        assert self.indent > 0
        self.indent = self.indent - 1

    def print(self, s):
        line = ""
        line += "\t" * self.indent
        line += s
        print(line)


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main(sock).run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise
